package com.zuoyi.sqlSession;

import com.zuoyi.config.BoundSql;
import com.zuoyi.pojo.Configuration;
import com.zuoyi.pojo.MappedStatement;
import com.zuoyi.utils.GenericTokenParser;
import com.zuoyi.utils.ParameterMapping;
import com.zuoyi.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {

    public int executeUpdate(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception{
        // 1. 加载数据库驱动 获取数据库连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql 并替换占位符为？
        String oldSql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(oldSql);

        // 3.获取预处理的preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数 先从实体拿到第一个占位符名称对应的对象属性值的参数值，然后再给相应占位符赋值
        String paramterType = mappedStatement.getParamterType();
        Class<?> classType = getClassType(paramterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();

        //这里需要判断一下参数类型，如果参数类型是java常用的数据类型，那么直接传入参数，否则才通过反射处理设置参数信息
        if(isJavaClass(classType)){
            for(int i= 0;i<params.length;i++){
                preparedStatement.setObject(i+1,params[i]);
            }
        } else {
            //拿到#{} 中的参数名称进行遍历并赋值
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                String content = parameterMapping.getContent();

                // 反射
                //通过Class对象获取属性对象
                Field declaredField = classType.getDeclaredField(content);
                //再通过属性对象获取值
                declaredField.setAccessible(true);
                Object o = declaredField.get(params[0]);
                //设置参数 JDBC代码一样 哪个位置(从1开始)，参数值是什么
                preparedStatement.setObject(i + 1, o);

                //↑ 给该sql占位符完成参数值的赋值
            }
        }

        // 5. 执行sql 获取返回结果集 resultSet
        int resultSet = preparedStatement.executeUpdate();
        return resultSet;
    }

   /* public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception{

        // 1. 加载数据库驱动 获取数据库连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql 并替换占位符为？
        String oldSql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(oldSql);

        // 3.获取预处理的preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数 先从实体拿到第一个占位符名称对应的对象属性值的参数值，然后再给相应占位符赋值
        String paramterType = mappedStatement.getParamterType();
        Class<?> classType = getClassType(paramterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();

        //这里需要判断一下参数类型，如果参数类型是java常用的数据类型，那么直接传入参数，否则才通过反射处理设置参数信息
        if(isJavaClass(classType)){
            for(int i= 0;i<params.length;i++){
                preparedStatement.setObject(i+1,params[i]);
            }
        } else {
            //拿到#{} 中的参数名称进行遍历并赋值
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                String content = parameterMapping.getContent();

                // 反射
                //通过Class对象获取属性对象
                Field declaredField = classType.getDeclaredField(content);
                //再通过属性对象获取值
                declaredField.setAccessible(true);
                Object o = declaredField.get(params[0]);
                //设置参数 JDBC代码一样 哪个位置(从1开始)，参数值是什么
                preparedStatement.setObject(i + 1, o);

                //↑ 给该sql占位符完成参数值的赋值
            }
        }

        // 5. 执行sql 获取返回结果集 resultSet
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> ResultClassType = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList();

        //6.封装返回结果集
        while (resultSet.next()) {
            Object o = ResultClassType.newInstance();
            //先获取一个元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //获取字段名
                String columnName = metaData.getColumnName(i);
                //获取字段值
                Object value = resultSet.getObject(columnName);

                *//**
                 * 自定义持久层框架 需要实体中的属性名和表中的字段名一致 ，mybatis呢？ TODO 查询结果的列名 和 实体中的字段名 以及sql语句起别名的底层是怎样一种关系？
                 *//*

                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, ResultClassType);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }
*/
    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if (paramterType != null) {
            return Class.forName(paramterType);
        } else {
            return null;

        }
    }

    /**
     * 完成对#{}的解析工作：1.将#{}使用？进行代替，2.解析出#{}里面的值进行存储
     * @param oldSql
     * @return
     * ParameterMappingTokenHandler ：标记处理类
     * TokenHandler : 标记处理器
     * GenericTokenParser : 标记解析器
     */
    private BoundSql getBoundSql(String oldSql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        String parseSql = genericTokenParser.parse(oldSql);
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
        return boundSql;
    }

    /**
     *该方法用来判断 传入的Class对象是不是新定义的Java实体类型
     * true代表不是实体类型
     */
    public Boolean isJavaClass(Class<?> aClass){
        return aClass != null && aClass.getClassLoader() == null;
    }

}
